#!/usr/bin/env python3
# Copyright 2021 - 2021, Gerard Patel
# SPDX-License-Identifier: GPL-3.0-or-later

# tool to examine the localized version of a language file
# in comparaison to the updated international english version
# and optionally generate new, updated version - use -h for usage

from collections.abc import Mapping
import json
import argparse
import os
import sys


# generate missing subkeys for a not found new key
def all_subkeys(detail, kref, level):
    cpt = 0
    for cle in kref:
        valref = kref[cle]
        if not isinstance(valref, Mapping):
            if detail:
                print(" / ".join(level + [cle]))
            cpt += 1
        else:
            level.append(cle)
            cpt += all_subkeys(detail, valref, level)
            level.pop()
    return cpt


# compare 'kcomp' tree to 'kref' reference version
def cmpref(detail, kref, kcomp, level):
    cpt = 0
    cptref = 0
    for cle in kref:
        if not cle in kcomp:
            if detail:
                print(" / ".join(level + [cle]))
            cpt += 1
            valref = kref[cle]
            if isinstance(valref, Mapping):
                level.append(cle)
                c = all_subkeys(detail, valref, level)
                cpt += c
                cptref += c
                level.pop()
            else:
                cptref += 1
        else:
            valref = kref[cle]
            if isinstance(valref, Mapping):
                level.append(cle)
                c1, c2 = cmpref(detail, valref, kcomp[cle], level)
                cpt += c1
                cptref += c2
                level.pop()
            else:
                cptref += 1
    return cpt, cptref


# read localized and international english files
# from current directory or lang/ subdirectory.
# If the program is launched to only display (not generate)
# then appropriate error messages are printed
def read_files(local_language_code, detail, generate):
    try_dir = ""
    key_eng = key_loc = {}
    local_language_file_name = "main-" + local_language_code + ".json"
    if not os.path.isfile(local_language_file_name):
        try_dir = "lang/"
        local_language_file_name = try_dir + local_language_file_name
        if not os.path.isfile(local_language_file_name):
            msg = (
                "file %s can't be found, verify language code."
                % local_language_file_name
            )
            if detail:
                print(msg)
            elif not generate:  # -s is used for global reports (called from script)
                print("10000;-1;0;useless (%s)" % msg)
            exit(1)
    try:
        with open(local_language_file_name) as f:
            key_loc = json.load(f)
    except Exception as e:
        if detail or generate:
            raise
        else:
            print("10000;-1;0;useless (parsing error: " + str(e))
            exit(1)
    if generate:
        # get the original indent to create a new file
        with open(local_language_file_name) as f:
            s = f.read().split("\n")[1]  # second line of original language file
            sniffed_indent = len(s) - len(s.lstrip())
            if not sniffed_indent:
                print(
                    "warning: could not find an valid indent value in original language file, forced to 2."
                )
                sniffed_indent = 2
    else:
        sniffed_indent = 0
    with open(try_dir + "main.json") as f:
        key_eng = json.load(f)
    return (key_eng, key_loc, sniffed_indent)


# print a localized file status on a single line if detail is false
# if detail is true:
# print all missing and obsolete keys
# if not:
# print number of new messages to add, number or obsolete messages, status
# key_loc, key_eng are dicts holding local and international keys
# local_language code is used to display appropriate error message
def display_file(local_language_code, detail, key_eng, key_loc):
    status = ""
    cpt1 = cpt2 = -1
    if detail:
        print(
            "english language keys that can't be found in language '%s' (to create)"
            % local_language_code
        )
    cpt1, cpttot = cmpref(detail, key_eng, key_loc, level=[])
    if detail:
        print("number of keys to add %d" % cpt1)
        print(
            "\n'%s' language keys that can't be found in english version (to be cleaned)"
            % local_language_code
        )
    cpt2, _ = cmpref(detail, key_loc, key_eng, level=[])
    if detail:
        print("number of keys to purge %d" % cpt2)
    else:
        # one line display only
        if not status:
            if cpt1 < 30:
                status = "decent"
            elif cpt1 < 100:
                status = "passable"
            elif cpt1 < 200:
                status = "bad"
            else:
                status = "very bad"
        pc = float(cpttot-cpt1)/float(cpttot)
        print("%d;%d;%0.3f;%s" % (cpt1, cpt2, pc, status))


# generate a new dict with missing keys added with 'prefix'
# added at the value's beginning:
# somekey: '!!english message to edit'
def generate_new_dict(kref, key_loc, key_pre, key_new, prefix):
    local_new = local_added_from_ref = 0
    for cle in kref:
        valref = kref[cle]
        if isinstance(valref, Mapping):
            key_new[cle] = {}
            if cle in key_loc:
                key_locnew = key_loc[cle]
            else:
                key_locnew = {}
            if cle in key_pre:
                key_prenew = key_pre[cle]
            else:
                key_prenew = {}
            l, a = generate_new_dict(
                valref, key_locnew, key_prenew, key_new[cle], prefix
            )
            local_new += l
            local_added_from_ref += a
        else:
            if cle in key_loc:
                key_new[cle] = key_loc[cle]
            elif cle in key_pre:
                key_new[cle] = key_pre[cle]
                local_added_from_ref += 1
            else:
                key_new[cle] = prefix + kref[cle]
                local_new += 1
    return (local_new, local_added_from_ref)


# ensure that the string 's' is terminated by a line feed
# to make the file format fully compatible with the existing project.
def ensure_lf(s):
    c = s[-1:]
    if c != b"\n":
        return s + b"\n"
    else:
        return s


if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        description="Examine and generate localized languages files"
    )
    parser.add_argument(
        "language_code",
        metavar="<language code>",
        nargs=1,
        help="localized language code",
    )
    parser.add_argument(
        "-s", "--summary", action="store_true", help="doing just a one-line report"
    )
    parser.add_argument(
        "-a",
        "--auto-generate",
        action="store_true",
        help="create a new file with new key values extracted from reference international english file",
    )
    parser.add_argument(
        "-o",
        "--output",
        help="specify a file path for generated output, set by default in the home directory",
    )
    parser.add_argument(
        "-p",
        "--prefix",
        default="!!",
        help="string added before the added untranslated keys in output file (set by default to '!!')",
    )
    parser.add_argument(
        "-r",
        "--reference-file",
        help="optional previously translated file from which already translated keys will be taken",
    )
    parser.add_argument(
        "-f", "--force", action="store_true", help="force generate even if no new keys"
    )
    parser.add_argument(
        "-i",
        "--indent",
        type=int,
        help="optional override of existing localized file indent",
    )
    options = parser.parse_args()
    language_code = options.language_code[0]
    if not language_code:
        print(
            "please pass localized language prefix in parameter, ex: enGB for UK english."
        )
        exit(1)
    if options.output:
        options.auto_generate = True
        if not os.path.isfile(options.output):
            options.output = os.path.realpath(options.output)
    if options.auto_generate and not options.output:
        options.output = os.path.expanduser("~")
    if options.summary and options.auto_generate:
        print("options summary and auto-generate are not compatible")
        exit(1)
    if options.output:
        if os.path.isdir(options.output):
            options.output = options.output + ("/main-%s.json" % language_code)
        try:
            open(options.output + "~", "w")
        except PermissionError:
            print(
                "you don't have permission to write to %s"
                % os.path.split(options.output)[0]
            )
            exit(1)
        os.remove(options.output + "~")
    detail = not options.summary
    generate = options.auto_generate
    key_eng, key_loc, sniffed_indent = read_files(language_code, detail, generate)
    if not options.auto_generate:
        display_file(language_code, detail, key_eng, key_loc)
    else:
        indent = options.indent
        if not indent:
            indent = sniffed_indent
        key_new = {}
        key_pre = {}
        if options.reference_file:
            ref_file = os.path.realpath(options.reference_file)
            if os.path.isfile(ref_file):
                with open(ref_file) as f:
                    key_pre = json.load(f)
        new_keys, added_from_ref = generate_new_dict(
            key_eng, key_loc, key_pre, key_new, options.prefix
        )
        if not new_keys and not options.force:
            print("nothing to generate")
        else:
            if os.path.isfile(options.output) and not options.force:
                print("file already exists, use -f option to force overwrite")
                exit(0)
            s = ensure_lf(
                json.dumps(key_new, indent=indent, ensure_ascii=False).encode("utf8")
            )
            with open(options.output, "wb") as f:
                f.write(s)
            print(
                "new version created as %s, %d new key(s) from which %d were added from a previous version"
                % (options.output, new_keys + added_from_ref, added_from_ref)
            )
